﻿using System;
using System.Collections.Generic;
using System.Linq;
using Plugins;
using Plugins.Core;
using StockModel;
using TechnicalIndicators.Points;
using Utils;
using Utils.Extensions;

namespace StockAnalyst2.Plugins
{
	internal class ExecutionResultStatisticsCalculator
	{
		#region Properties

		public ExecutionResultStatistics Statistics { get; private set; }

		#endregion

		#region Public Methods

		public ExecutionResultStatisticsCalculator()
		{
			this.Statistics = new ExecutionResultStatistics();	
		}

		public void CalculateStatistics(HistoricalExecutionResult result)
		{
			this.Statistics.Clear();

			this.Statistics.TotalOperations = result.Operations.Count;
			this.Statistics.WinOperations = result.Operations.Count(o => o.IsWinner);
			this.Statistics.LossOperations = result.Operations.Count(o => !o.IsWinner);
			this.Statistics.TotalResult = result.Operations.Sum(o => o.Result);
			this.Statistics.MaxWinOperation = this.Statistics.WinOperations > 0 ? result.Operations.Where(o => o.IsWinner).Max(o => o.Result) : 0;
			this.Statistics.MaxLossOperation = this.Statistics.LossOperations > 0 ? result.Operations.Where(o => !o.IsWinner).Min(o => o.Result) : 0;
			this.Statistics.AverageOperation = result.Operations.Count != 0 ? this.Statistics.TotalResult / result.Operations.Count() : 0;
			this.Statistics.AverageGrossOperation = result.Operations.Count != 0 ? result.Operations.Sum(o => o.GrossResult) / result.Operations.Count : 0;
			this.Statistics.TotalWin = result.Operations.Where(o => o.IsWinner).Sum(o => o.Result);
			this.Statistics.AverageWin = this.Statistics.WinOperations != 0 ? result.Operations.Where(o => o.IsWinner).Sum(o => o.Result) / this.Statistics.WinOperations : 0;
			this.Statistics.TotalLoss = result.Operations.Where(o => !o.IsWinner).Sum(o => o.Result);
			this.Statistics.AverageLoss = this.Statistics.LossOperations != 0 ? result.Operations.Where(o => !o.IsWinner).Sum(o => o.Result) / this.Statistics.LossOperations : 0;

			this.CalculatePartials(result);
			this.CalculateBestAndWorst();

			this.Statistics.TotalDays = (int)(result.EvaluatedDays.To - result.EvaluatedDays.From).TotalDays;
			this.Statistics.MaxAmountRequired = this.Statistics.AccumulatedOperationPartials.Count > 0 ? this.Statistics.AmountsRequired.Max(v => v.Y) : 0;
			this.Statistics.BestResultPercentage = this.Statistics.MaxAmountRequired != 0 ? this.Statistics.BestResult * 100 / this.Statistics.MaxAmountRequired : 0;
			this.Statistics.WorstResultPercentage = this.Statistics.MaxAmountRequired != 0 ? this.Statistics.WorstResult * 100 / this.Statistics.MaxAmountRequired : 0;
			this.Statistics.MaxWin = this.Statistics.AccumulatedOperationPartials.Count > 0 ? this.Statistics.AccumulatedOperationPartials.Max(v => v.Y) : 0;
			this.Statistics.MaxLoss = this.Statistics.AccumulatedOperationPartials.Count > 0 ? this.Statistics.AccumulatedOperationPartials.Min(v => v.Y) : 0;
			this.Statistics.Profit = this.Statistics.MaxAmountRequired > 0 ? this.Statistics.TotalResult * 100 / this.Statistics.MaxAmountRequired : 0;
			this.Statistics.DailyProfit = this.Statistics.TotalDays > 0 ? this.Statistics.Profit / this.Statistics.TotalDays : 0;
			
			this.Statistics.WinOperationsPercentage = result.Operations.Count > 0 ? this.Statistics.WinOperations * 100 / result.Operations.Count : 0;
			this.Statistics.LossOperationsPercentage = result.Operations.Count > 0 ? this.Statistics.LossOperations * 100 / result.Operations.Count : 0;
		}

		#endregion

		#region Private Methods

		private void CalculatePartials(HistoricalExecutionResult result)
		{
			// Operation partials
			decimal accumulatedValue = 0;
			decimal previousAccumulatedValue = 0;
			foreach (Operation operation in result.Operations)
			{
				accumulatedValue = accumulatedValue + operation.Result;
				this.Statistics.AmountsRequired.Add(new DatedSeriePoint(operation.OpeningDate, operation.AmountRequired - previousAccumulatedValue));
				this.Statistics.AccumulatedOperationPartials.Add(new DatedSeriePoint(operation.OpeningDate, accumulatedValue));
				previousAccumulatedValue = accumulatedValue;
			}

			// Daily partials
			accumulatedValue = 0;
			IEnumerable<DateTime> days = result.Operations.Select(o => o.OpeningDate.Date).Distinct();
			foreach (DateTime day in days)
			{
				DateTime partialDay = day;
				decimal dailyValue = result.Operations.Where(o => o.OpeningDate.Date == partialDay.Date).Sum(o => o.Result);
				accumulatedValue = accumulatedValue + dailyValue;

				this.Statistics.DailyPartials.Add(new DatedSeriePoint(day, dailyValue));
				this.Statistics.AccumulatedDailyPartials.Add(new DatedSeriePoint(day, accumulatedValue));
			}
		}

		private void CalculateBestAndWorst()
		{
			if (this.Statistics.DailyPartials.Count > 0)
			{
				List<decimal> dailyResults = this.Statistics.DailyPartials.Select(d => d.Y).ToList();

				int startIndex;
				int endIndex;

				this.Statistics.BestResult = dailyResults.FindBestSubsequence(out startIndex, out endIndex);
				this.Statistics.BestResultPeriod = new DateRange(this.Statistics.DailyPartials[startIndex].X, this.Statistics.DailyPartials[endIndex].X);
				this.Statistics.WorstResult = dailyResults.FindWorstSubsequence(out startIndex, out endIndex);
				this.Statistics.WorstResultPeriod = new DateRange(this.Statistics.DailyPartials[startIndex].X, this.Statistics.DailyPartials[endIndex].X);
			}
		}

		#endregion
	}
}
